﻿//++
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Module Name:
//
//  Logger.cs
//    
// Abstract:
//
//  This module implements the Logger class.
//  
//--

using System;
using System.Diagnostics;
using System.Diagnostics.Eventing;
using System.Globalization;
using System.Text;

namespace Microsoft.OfficeCommunicationsServer.Applications.Common
{
    /// <summary>
    /// Enumeration that controls the level of diagnostic logging.
    /// </summary>
    public enum TraceLogLevel
    {
        /// <summary>
        /// No diagnostic logging.
        /// </summary>
        None = 0,

        /// <summary>
        /// Log errors to the diagnostic logs.
        /// </summary>
        Errors = 1,

        /// <summary>
        /// Log errors and warnings to the diagnostic logs. 
        /// </summary>
        Warnings = 2,

        /// <summary>
        /// Log errors, warnings and information to the diagnostic logs.
        /// </summary>
        Information = 3,

        /// <summary>
        /// Log all messages to the diagnostic logs.
        /// </summary>
        All = 4
    }

    /// <summary>
    /// Enumeration that specifies the type of log entry
    /// </summary>
    /// <remarks>
    /// The values match those defined for the level field of the EVENT_TRACE_HEADER structure used in ETW.
    /// </remarks>
    public enum LogEntryType
    {
        /// <summary>
        /// Error
        /// </summary>
        Error = 2,

        /// <summary>
        /// Warning
        /// </summary>
        Warning = 3,

        /// <summary>
        /// Information
        /// </summary>
        Information = 4,

        /// <summary>
        /// Verbose
        /// </summary>
        Verbose = 5
    }

    /// <summary>
    /// Logger implementation.
    /// </summary>
    public static class Logger
    {
        // XML tags used to format the diagnostic message when it is written to ETW
        private const string SourceStartTag = "<source>";
        private const string SourceEndTag = "</source>";
        private const string CategoryStartTag = "<category>";
        private const string CatgeoryEndTag = "</category>";
        private const string MessageStartTag = "<message>";
        private const string MessageEndTag = "</message>";
        private const string ErrorStartTag = "<error>";
        private const string ErrorEndTag = "</error>";

        private static EtwTraceProvider _etwTraceProvider;
        private static TraceLogLevel _traceLogLevel;
        private static string _source;
        private static bool _traceOutput;
        private static object _syncRoot = new object();

        /// <summary>
        /// Initializes the logger for both diagnostic and operational logging.
        /// </summary>
        /// <param name="source">Source name for logs</param>
        /// <param name="traceLogLevel">Initial diagnostic logging level</param>
        /// <param name="traceOutput">Indicates if diagnostic logs should be written to System.Diagnostic.Trace</param>
        public static void Initialize(string source, TraceLogLevel traceLogLevel, bool traceOutput)
        {
            Initialize(source, traceLogLevel, traceOutput, true /* enableOperationalLogs */);
        }

        /// <summary>
        /// Initializes the logger.
        /// </summary>
        /// <param name="source">Source name for logs</param>
        /// <param name="traceLogLevel">Initial diagnostic logging level</param>
        /// <param name="traceOutput">Indicates if diagnostic logs should be written to System.Diagnostic.Trace</param>
        /// <param name="enableOperationalLogs">Indicates if operational logging should be enabled</param>
        /// <remarks>
        /// Operational logging is optional because it requires admin privileges and is not used by client applications.
        /// </remarks>
        public static void Initialize(string source, TraceLogLevel traceLogLevel, bool traceOutput, bool enableOperationalLogs)
        {
            Debug.Assert(!String.IsNullOrEmpty(source), "!String.IsNullOrEmpty(source)");

            // Initialize should only be called once
            Debug.Assert(_etwTraceProvider == null, "_etwTraceProvider == null");

            lock (_syncRoot) // The lock is necessary to prevent Close being executed before this method completes
            {
                if (enableOperationalLogs && !EventLog.SourceExists(source))
                {
                    // If an event source doesn't already exist, register the event source with the Application log
                    EventLog.CreateEventSource(source, null /* logName */);
                }

                // Ensure logging is shutdown cleanly before the process terminates
                AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionHandler;
                AppDomain.CurrentDomain.DomainUnload += ExitOrUnloadEventHandler;
                AppDomain.CurrentDomain.ProcessExit += ExitOrUnloadEventHandler;

                // Initialize member variables
                _source = source;
                _traceLogLevel = traceLogLevel;
                _traceOutput = traceOutput;

                // Create ETW provider
                _etwTraceProvider = new EtwTraceProvider();
            }
        }

        /// <summary>
        /// Closes the trace listeners and ensures everything is written to the logs.
        /// </summary>
        public static void Close()
        {
            lock (_syncRoot) // The lock is necessary becauase Close might be called on multiple threads simultaneously
            {
                if (_etwTraceProvider != null)
                {
                    AppDomain.CurrentDomain.UnhandledException -= UnhandledExceptionHandler;
                    AppDomain.CurrentDomain.DomainUnload -= ExitOrUnloadEventHandler;
                    AppDomain.CurrentDomain.ProcessExit -= ExitOrUnloadEventHandler;

                    _etwTraceProvider.Close();
                    _etwTraceProvider = null;

                    Trace.Flush();
                }
            }
        }

        /// <summary>
        /// Level of diagnostic logging.
        /// </summary>
        public static TraceLogLevel LoggingLevel
        {
            get
            {
                return _traceLogLevel;
            }
            set
            {
                _traceLogLevel = value;
            }
        }

        /// <summary>
        /// Indicates if diagnostic logging is enabled for the specified type of log entry.
        /// </summary>
        /// <param name="logEntryType">Type of log entry</param>
        /// <returns>
        /// true if logging is enabled for the specified type; false otherwise.
        /// </returns>
        public static bool IsEnabled(LogEntryType logEntryType)
        {
            switch (logEntryType)
            {
                case LogEntryType.Error:
                    return _traceLogLevel > TraceLogLevel.None;
                case LogEntryType.Warning:
                    return _traceLogLevel > TraceLogLevel.Errors;
                case LogEntryType.Information:
                    return _traceLogLevel > TraceLogLevel.Warnings;
                case LogEntryType.Verbose:
                    return _traceLogLevel > TraceLogLevel.Information;
                default:
                    Debug.Fail(String.Format(CultureInfo.InvariantCulture, "Unexpected TracelogEntryType: {0}", logEntryType));
                    return false;
            }
        }

        /// <summary>
        /// Writes an error message to the diagnostic logs.
        /// </summary>
        /// <param name="category">Category associated with this log</param>
        /// <param name="context">Context associated with this log</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceError(string category, Context context, string format, params object[] args)
        {
            Debug.Assert(context != null, "context != null");
            Debug.Assert(!string.IsNullOrEmpty(category), "!string.IsNullOrEmpty(category)");
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");

            WriteTraceEvent(LogEntryType.Error, category, context, String.Format(CultureInfo.InvariantCulture, format, args), null);
        }

        /// <summary>
        /// Writes an error message to the diagnostic logs.
        /// </summary>
        /// <param name="category">Category associated with this log</param>
        /// <param name="context">Context associated with this log</param>
        /// <param name="error">Exception associated with this error</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceError(string category, Context context, Exception error, string format, params object[] args)
        {
            Debug.Assert(context != null, "context != null");
            Debug.Assert(!string.IsNullOrEmpty(category), "!string.IsNullOrEmpty(category)");
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");
            Debug.Assert(error != null, "error != null");

            WriteTraceEvent(LogEntryType.Error, category, context, String.Format(CultureInfo.InvariantCulture, format, args), error);
        }

        /// <summary>
        /// Writes an error message to the diagnostic logs.
        /// </summary>
        /// <param name="context">Context associated with this log</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceError(Context context, string format, params object[] args)
        {
            Debug.Assert(context != null, "context != null");
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");

            WriteTraceEvent(LogEntryType.Error, null, context, String.Format(CultureInfo.InvariantCulture, format, args), null);
        }

        /// <summary>
        /// Writes an error message to the diagnostic logs.
        /// </summary>
        /// <param name="context">Context associated with this log</param>
        /// <param name="error">Exception associated with this error</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceError(Context context, Exception error, string format, params object[] args)
        {
            Debug.Assert(context != null, "context != null");
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");
            Debug.Assert(error != null, "error != null");

            WriteTraceEvent(LogEntryType.Error, null, context, String.Format(CultureInfo.InvariantCulture, format, args), error);
        }

        /// <summary>
        /// Writes an error message to the diagnostic logs.
        /// </summary>
        /// <param name="category">Category associated with this log</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceError(string category, string format, params object[] args)
        {
            Debug.Assert(!string.IsNullOrEmpty(category), "!string.IsNullOrEmpty(category)");
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");

            WriteTraceEvent(LogEntryType.Error, category, Context.Global, String.Format(CultureInfo.InvariantCulture, format, args), null);
        }

        /// <summary>
        /// Writes an error message to the diagnostic logs.
        /// </summary>
        /// <param name="category">Category associated with this log</param>
        /// <param name="error">Exception associated with this error</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceError(string category, Exception error, string format, params object[] args)
        {
            Debug.Assert(!string.IsNullOrEmpty(category), "!string.IsNullOrEmpty(category)");
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");
            Debug.Assert(error != null, "error != null");

            WriteTraceEvent(LogEntryType.Error, category, Context.Global, String.Format(CultureInfo.InvariantCulture, format, args), error);
        }

        /// <summary>
        /// Writes an error message to the diagnostic logs.
        /// </summary>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceError(string format, params object[] args)
        {
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");

            WriteTraceEvent(LogEntryType.Error, null, Context.Global, String.Format(CultureInfo.InvariantCulture, format, args), null);
        }

        /// <summary>
        /// Writes an error message to the diagnostic logs.
        /// </summary>
        /// <param name="error">Exception associated with this error</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceError(Exception error, string format, params object[] args)
        {
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");
            Debug.Assert(error != null, "error != null");

            WriteTraceEvent(LogEntryType.Error, null, Context.Global, String.Format(CultureInfo.InvariantCulture, format, args), error);
        }
        
        /// <summary>
        /// Writes a warning message to the diagnostic logs.
        /// </summary>
        /// <param name="category">Category associated with this log</param>
        /// <param name="context">Context associated with this log</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceWarning(string category, Context context, string format, params object[] args)
        {
            Debug.Assert(context != null, "context != null");
            Debug.Assert(!string.IsNullOrEmpty(category), "!string.IsNullOrEmpty(category)");
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");

            WriteTraceEvent(LogEntryType.Warning, category, context, String.Format(CultureInfo.InvariantCulture, format, args), null);
        }

        /// <summary>
        /// Writes an error message to the diagnostic logs.
        /// </summary>
        /// <param name="category">Category associated with this log</param>
        /// <param name="context">Context associated with this log</param>
        /// <param name="error">Exception associated with this error</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceWarning(string category, Context context, Exception error, string format, params object[] args)
        {
            Debug.Assert(context != null, "context != null");
            Debug.Assert(!string.IsNullOrEmpty(category), "!string.IsNullOrEmpty(category)");
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");
            Debug.Assert(error != null, "error != null");

            WriteTraceEvent(LogEntryType.Warning, category, context, String.Format(CultureInfo.InvariantCulture, format, args), error);
        }

        /// <summary>
        /// Writes a warning message to the diagnostic logs.
        /// </summary>
        /// <param name="context">Context associated with this log</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceWarning(Context context, string format, params object[] args)
        {
            Debug.Assert(context != null, "context != null");
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");

            WriteTraceEvent(LogEntryType.Warning, null, context, String.Format(CultureInfo.InvariantCulture, format, args), null);
        }

        /// <summary>
        /// Writes an error message to the diagnostic logs.
        /// </summary>
        /// <param name="context">Context associated with this log</param>
        /// <param name="error">Exception associated with this error</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceWarning(Context context, Exception error, string format, params object[] args)
        {
            Debug.Assert(context != null, "context != null");
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");
            Debug.Assert(error != null, "error != null");

            WriteTraceEvent(LogEntryType.Warning, null, context, String.Format(CultureInfo.InvariantCulture, format, args), error);
        }

        /// <summary>
        /// Writes a warning message to the diagnostic logs.
        /// </summary>
        /// <param name="category">Category associated with this log</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceWarning(string category, string format, params object[] args)
        {
            Debug.Assert(!string.IsNullOrEmpty(category), "!string.IsNullOrEmpty(category)");
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");

            WriteTraceEvent(LogEntryType.Warning, category, Context.Global, String.Format(CultureInfo.InvariantCulture, format, args), null);
        }

        /// <summary>
        /// Writes an error message to the diagnostic logs.
        /// </summary>
        /// <param name="category">Category associated with this log</param>
        /// <param name="error">Exception associated with this error</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceWarning(string category, Exception error, string format, params object[] args)
        {
            Debug.Assert(!string.IsNullOrEmpty(category), "!string.IsNullOrEmpty(category)");
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");
            Debug.Assert(error != null, "error != null");

            WriteTraceEvent(LogEntryType.Warning, category, Context.Global, String.Format(CultureInfo.InvariantCulture, format, args), error);
        }

        /// <summary>
        /// Writes a warning message to the diagnostic logs.
        /// </summary>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceWarning(string format, params object[] args)
        {
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");

            WriteTraceEvent(LogEntryType.Warning, null, Context.Global, String.Format(CultureInfo.InvariantCulture, format, args), null);
        }

        /// <summary>
        /// Writes an error message to the diagnostic logs.
        /// </summary>
        /// <param name="error">Exception associated with this error</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceWarning(Exception error, string format, params object[] args)
        {
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");
            Debug.Assert(error != null, "error != null");

            WriteTraceEvent(LogEntryType.Warning, null, Context.Global, String.Format(CultureInfo.InvariantCulture, format, args), error);
        }
        
        /// <summary>
        /// Writes an informational message to the diagnostic logs.
        /// </summary>
        /// <param name="category">Category associated with this log</param>
        /// <param name="context">Context associated with this log</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceInformation(string category, Context context, string format, params object[] args)
        {
            Debug.Assert(context != null, "context != null");
            Debug.Assert(!string.IsNullOrEmpty(category), "!string.IsNullOrEmpty(category)");
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");

            WriteTraceEvent(LogEntryType.Information, category, context, String.Format(CultureInfo.InvariantCulture, format, args), null);
        }

        /// <summary>
        /// Writes an informational message to the diagnostic logs.
        /// </summary>
        /// <param name="context">Context associated with this log</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceInformation(Context context, string format, params object[] args)
        {
            Debug.Assert(context != null, "context != null");
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");

            WriteTraceEvent(LogEntryType.Information, null, context, String.Format(CultureInfo.InvariantCulture, format, args), null);
        }

        /// <summary>
        /// Writes an informational message to the diagnostic logs.
        /// </summary>
        /// <param name="category">Category associated with this log</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceInformation(string category, string format, params object[] args)
        {
            Debug.Assert(!string.IsNullOrEmpty(category), "!string.IsNullOrEmpty(category)");
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");

            WriteTraceEvent(LogEntryType.Information, category, Context.Global, String.Format(CultureInfo.InvariantCulture, format, args), null);
        }

        /// <summary>
        /// Writes an informational message to the diagnostic logs.
        /// </summary>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceInformation(string format, params object[] args)
        {
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");

            WriteTraceEvent(LogEntryType.Information, null, Context.Global, String.Format(CultureInfo.InvariantCulture, format, args), null);
        }

        /// <summary>
        /// Writes a verbose message to the diagnostic logs.
        /// </summary>
        /// <param name="category">Category associated with this log</param>
        /// <param name="context">Context associated with this log</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceVerbose(string category, Context context, string format, params object[] args)
        {
            Debug.Assert(context != null, "context != null");
            Debug.Assert(!string.IsNullOrEmpty(category), "!string.IsNullOrEmpty(category)");
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");

            WriteTraceEvent(LogEntryType.Verbose, category, context, String.Format(CultureInfo.InvariantCulture, format, args), null);
        }

        /// <summary>
        /// Writes a verbose message to the diagnostic logs.
        /// </summary>
        /// <param name="context">Context associated with this log</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceVerbose(Context context, string format, params object[] args)
        {
            Debug.Assert(context != null, "context != null");
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");

            WriteTraceEvent(LogEntryType.Verbose, null, context, String.Format(CultureInfo.InvariantCulture, format, args), null);
        }

        /// <summary>
        /// Writes a verbose message to the diagnostic logs.
        /// </summary>
        /// <param name="category">Category associated with this log</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceVerbose(string category, string format, params object[] args)
        {
            Debug.Assert(!string.IsNullOrEmpty(category), "!string.IsNullOrEmpty(category)");
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");

            WriteTraceEvent(LogEntryType.Verbose, category, Context.Global, String.Format(CultureInfo.InvariantCulture, format, args), null);
        }

        /// <summary>
        /// Writes a verbose message to the diagnostic logs.
        /// </summary>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteTraceVerbose(string format, params object[] args)
        {
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");

            WriteTraceEvent(LogEntryType.Verbose, null, Context.Global, String.Format(CultureInfo.InvariantCulture, format, args), null);
        }

        /// <summary>
        /// Writes an error message to the operational logs.
        /// </summary>
        /// <param name="eventId">ID associated with this log message</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteOperationalError(int eventId, string format, params object[] args)
        {
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");

            WriteOperationalEvent(LogEntryType.Error, eventId, String.Format(CultureInfo.InvariantCulture, format, args), null);
        }

        /// <summary>
        /// Writes an error message to the operational logs.
        /// </summary>
        /// <param name="eventId">ID associated with this log message</param>
        /// <param name="error">Exception associated with this error</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteOperationalError(int eventId, Exception error, string format, params object[] args)
        {
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");
            Debug.Assert(error != null, "error != null");

            WriteOperationalEvent(LogEntryType.Error, eventId, String.Format(CultureInfo.InvariantCulture, format, args), error);
        }

        /// <summary>
        /// Writes a warning message to the operational logs.
        /// </summary>
        /// <param name="eventId">ID associated with this log message</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteOperationalWarning(int eventId, string format, params object[] args)
        {
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");

            WriteOperationalEvent(LogEntryType.Warning, eventId, String.Format(CultureInfo.InvariantCulture, format, args), null);
        }

        /// <summary>
        /// Writes a warning message to the operational logs.
        /// </summary>
        /// <param name="eventId">ID associated with this log message</param>
        /// <param name="error">Exception associated with this warning</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteOperationalWarning(int eventId, Exception error, string format, params object[] args)
        {
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");
            Debug.Assert(error != null, "error != null");

            WriteOperationalEvent(LogEntryType.Warning, eventId, String.Format(CultureInfo.InvariantCulture, format, args), error);
        }

        /// <summary>
        /// Writes an informational message to the operational logs.
        /// </summary>
        /// <param name="eventId">ID associated with this log message</param>
        /// <param name="format">Log message format string</param>
        /// <param name="args">Arguments to the message format string</param>
        public static void WriteOperationalInformation(int eventId, string format, params object[] args)
        {
            Debug.Assert(!string.IsNullOrEmpty(format), "!string.IsNullOrEmpty(format)");

            WriteOperationalEvent(LogEntryType.Information, eventId, String.Format(CultureInfo.InvariantCulture, format, args), null);
        }

        /// <summary>
        /// Writes an event to System.Diagnostic.Trace.
        /// </summary>
        /// <param name="logEntryType">Event type</param>
        /// <param name="message">Log message</param>
        private static void WriteSystemTrace(LogEntryType logEntryType, string message)
        {
            Debug.Assert(!string.IsNullOrEmpty(message), "!string.IsNullOrEmpty(message)");

            switch (logEntryType)
            {
                case LogEntryType.Error:
                    Trace.TraceError(message);
                    break;
                case LogEntryType.Warning:
                    Trace.TraceWarning(message);
                    break;
                case LogEntryType.Information:
                case LogEntryType.Verbose: // There isn't Trace.TraceVerbose, so have to use Trace.TraceInformation
                    Trace.TraceInformation(message);
                    break;
                default:
                    Debug.Fail(String.Format(CultureInfo.InvariantCulture, "Unexpected LogEntryType: {0}", logEntryType));
                    break;
            }
        }

        /// <summary>
        /// Formats the message for writing to System.Diagnostics.Trace.
        /// </summary>
        /// <param name="category">Category associated with this log</param>
        /// <param name="context">Context associated with this log</param>
        /// <param name="message">Log message</param>
        /// <param name="error">Exception associated with this log</param>
        /// <returns>
        /// The formatted message.
        /// </returns>
        /// <remarks>
        /// This method formats the data appropriately for System.Diagnostic.Trace. The format is much more condensed compared to 
        /// the format used with ETW to make it easier to read on one line.
        /// </remarks>
        private static string FormatMessageForTrace(string category, Context context, string message, Exception error)
        {
            Debug.Assert(context != null, "context != null");
            Debug.Assert(!string.IsNullOrEmpty(message), "!string.IsNullOrEmpty(message)");

            StringBuilder formattedMessage = new StringBuilder();

            if (!String.IsNullOrEmpty(category))
            {
                formattedMessage.AppendFormat(CultureInfo.InvariantCulture, "{0}, {1}, {2}, {3}", context, _source, category, message);
            }
            else
            {
                formattedMessage.AppendFormat(CultureInfo.InvariantCulture, "{0}, {1}, {2}", context, _source, message);
            }

            if (error != null)
            {
                formattedMessage.AppendFormat(CultureInfo.InvariantCulture, ", {0}", error);
            }

            return formattedMessage.ToString();
        }

        /// <summary>
        /// Writes an event to System.Diagnostic.Trace.
        /// </summary>
        /// <param name="logEntryType">Event type</param>
        /// <param name="category">Category associated with this log</param>
        /// <param name="context">Context associated with this log</param>
        /// <param name="message">Log message</param>
        /// <param name="error">Exception associated with this log</param>
        private static void WriteSystemTrace(LogEntryType logEntryType, string category, Context context, string message, Exception error)
        {
            Debug.Assert((int)logEntryType >= 2 && (int)logEntryType <= 5, "logEntryType >= 2 && logEntryType <= 5");
            Debug.Assert(context != null, "context != null");
            Debug.Assert(!string.IsNullOrEmpty(message), "!string.IsNullOrEmpty(message)");

            // Format the message appropriately for writing to System.Diagnostic.Trace
            String formattedMessage = FormatMessageForTrace(category, context, message, error);

            WriteSystemTrace(logEntryType, formattedMessage);
        }

        /// <summary>
        /// Formats the message for writing to ETW.
        /// </summary>
        /// <param name="category">Category associated with this log</param>
        /// <param name="context">Context associated with this log</param>
        /// <param name="message">Log message</param>
        /// <param name="error">Exception associated with this log</param>
        /// <returns>
        /// The formatted message.
        /// </returns>
        /// <remarks>This method formats the data appropriately for ETW. All the data is lgged in full in XML format.</remarks>
        private static string FormatMessageForEtw(string category, Context context, string message, Exception error)
        {
            Debug.Assert(context != null, "context != null");
            Debug.Assert(!string.IsNullOrEmpty(message), "!string.IsNullOrEmpty(message)");

            StringBuilder formattedMessage = new StringBuilder();

            if (!String.IsNullOrEmpty(category))
            {
                formattedMessage.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}{2}{3}{4}{5}",
                    SourceStartTag, _source, SourceEndTag, CategoryStartTag, category, CatgeoryEndTag);
            }
            else
            {
                formattedMessage.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}{2}",
                    SourceStartTag, _source, SourceEndTag);
            }

            string extendedProperties = context.ExtendedPropertiesToXmlString();

            if (!String.IsNullOrEmpty(extendedProperties))
            {
                formattedMessage.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}{2}{3}",
                    extendedProperties, MessageStartTag, message, MessageEndTag);
            }
            else
            {
                formattedMessage.AppendFormat(CultureInfo.InvariantCulture, "{0}{1}{2}",
                    MessageStartTag, message, MessageEndTag);
            }

            if (error != null)
            {
                // The exception contents might not be valid XML, so enclose them in a CDATA section
                formattedMessage.AppendFormat(CultureInfo.InvariantCulture, "{0}<![CDATA[{1}]]>{2}",
                    ErrorStartTag, error, ErrorEndTag);
            }

            return formattedMessage.ToString();
        }

        /// <summary>
        /// Writes an event to ETW.
        /// </summary>
        /// <param name="logEntryType">Event type</param>
        /// <param name="category">Category associated with this log</param>
        /// <param name="context">Context associated with this log</param>
        /// <param name="message">Log message</param>
        /// <param name="error">Exception associated with this log</param>
        private static void WriteEtwTrace(LogEntryType logEntryType, string category, Context context, string message, Exception error)
        {
            Debug.Assert((int)logEntryType >= 2 && (int)logEntryType <= 5, "logEntryType >= 2 && logEntryType <= 5");
            Debug.Assert(context != null, "context != null");
            Debug.Assert(!string.IsNullOrEmpty(message), "!string.IsNullOrEmpty(message)");

            // Take a local reference as access to _etwTraceProvider is not thread safe without locking _syncRoot
            EtwTraceProvider etwTraceProvider = _etwTraceProvider;

            // Check logging has been initialized and an ETW session has been started
            if (etwTraceProvider != null && etwTraceProvider.ShouldTrace)
            {
                // Format the message appropriately for writing to ETW.
                string formattedMessage = FormatMessageForEtw(category, context, message, error);

                // Write log to ETW
                etwTraceProvider.WriteTrace(context.ActivityId, (byte)logEntryType, formattedMessage);
            }
        }

        /// <summary>
        /// Writes an event to the diagnostic logs.
        /// </summary>
        /// <param name="logEntryType">Event type</param>
        /// <param name="category">Category associated with this log</param>
        /// <param name="context">Context associated with this log</param>
        /// <param name="message">Log message</param>
        /// <param name="error">Exception associated with this log</param>
        private static void WriteTraceEvent(LogEntryType logEntryType, string category, Context context, string message, Exception error)
        {
            Debug.Assert((int)logEntryType >= 2 && (int)logEntryType <= 5, "logEntryType >= 2 && logEntryType <= 5");
            Debug.Assert(context != null, "context != null");
            Debug.Assert(!string.IsNullOrEmpty(message), "!string.IsNullOrEmpty(message)");

            if (!IsEnabled(logEntryType))
            {
                // Diagnostic logging not enabled for this log event type
                return;
            }

            // Write log to ETW
            WriteEtwTrace(logEntryType, category, context, message, error);

            if (_traceOutput)
            {
                // Write log to System.Diagnostics.Trace
                WriteSystemTrace(logEntryType, category, context, message, error);
            }
        }

        /// <summary>
        /// Writes an event to the event log.
        /// </summary>
        /// <param name="logEntryType">Type of event</param>
        /// <param name="eventId">ID associated with this log message</param>
        /// <param name="message">Log message</param>
        private static void WriteEventLogEntry(LogEntryType logEntryType, int eventId, string message)
        {
            Debug.Assert(!string.IsNullOrEmpty(message), "!string.IsNullOrEmpty(message)");

            if (!String.IsNullOrEmpty(_source))
            {
                switch (logEntryType)
                {
                    case LogEntryType.Error:
                        EventLog.WriteEntry(_source, message, EventLogEntryType.Error, eventId);
                        break;
                    case LogEntryType.Warning:
                        EventLog.WriteEntry(_source, message, EventLogEntryType.Warning, eventId);
                        break;
                    case LogEntryType.Information:
                        EventLog.WriteEntry(_source, message, EventLogEntryType.Information, eventId);
                        break;
                    default:
                        Debug.Fail(String.Format(CultureInfo.InvariantCulture, "Unexpected LogEntryType: {0}", logEntryType));
                        break;
                }
            }
        }

        /// <summary>
        /// Writes an event to the operational logs.
        /// </summary>
        /// <param name="logEntryType">Type of event</param>
        /// <param name="eventId">ID associated with this log message</param>
        /// <param name="message">Log message</param>
        /// <param name="error">Exception associated with this log</param>
        private static void WriteOperationalEvent(LogEntryType logEntryType, int eventId, string message, Exception error)
        {
            Debug.Assert(!string.IsNullOrEmpty(message), "!string.IsNullOrEmpty(message)");
            Debug.Assert(logEntryType != LogEntryType.Verbose, "logEntryType != LogEntryType.Verbose");

            if (error != null && !String.IsNullOrEmpty(error.Message))
            {
                // Write event to the event log including the exception message
                WriteEventLogEntry(logEntryType, eventId, String.Format(CultureInfo.InvariantCulture, LogStrings.ErrorMessage, message, error.Message));
            }
            else
            {
                // Write event to the event log
                WriteEventLogEntry(logEntryType, eventId, message);
            }

            // Write event to the diagnostic log
            WriteTraceEvent(logEntryType, null, Context.Global, message, error);
        }

        /// <summary>
        /// Closes logging when the process exits.
        /// </summary>
        /// <param name="sender">Not used</param>
        /// <param name="e">Not used</param>
        private static void ExitOrUnloadEventHandler(object sender, EventArgs e)
        {
            Close();
        }

        /// <summary>
        /// Closes logging when there is an unhandled exception.
        /// </summary>
        /// <param name="sender">Not used</param>
        /// <param name="args">Not used</param>
        private static void UnhandledExceptionHandler(object sender, UnhandledExceptionEventArgs args)
        {
            Close();
        }
    }
}
